#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (C) 2010 Jose Aliste <jose.aliste@gmail.com>
#               2011 Benjamin Kellermann <Benjamin.Kellermann@tu-dresden.de>
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public Licence as published by the Free Software
# Foundation; either version 2 of the Licence, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public Licence for more
# details.
#
# You should have received a copy of the GNU General Public Licence along with
# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
# Street, Fifth Floor, Boston, MA  02110-1301, USA

# Incorporates fixes from http://ubuntuforums.org/showthread.php?t=1716268
from __future__ import print_function

import dbus
import subprocess
import time
import sys
import traceback

if sys.version_info < (3,):
    from urllib import unquote as urllib_unquote
    from urllib import quote

    def unquote(s):
        # This is to deal with source files with non-ascii names
        # We get url-quoted UTF-8 from dbus; convert to url-quoted ascii
        # and then unquote. If you don't first convert ot ascii, it fails.
        # It's a bit magical, but it seems to work
        return urllib_unquote(s.encode('ascii'))
else:
    from urllib.parse import unquote
    from urllib.parse import quote

RUNNING, CLOSED = range(2)

EV_DAEMON_PATH = "/org/gnome/evince/Daemon"
EV_DAEMON_NAME = "org.gnome.evince.Daemon"
EV_DAEMON_IFACE = "org.gnome.evince.Daemon"

EVINCE_PATH = "/org/gnome/evince/Evince"
EVINCE_IFACE = "org.gnome.evince.Application"

EV_WINDOW_IFACE = "org.gnome.evince.Window"


class EvinceWindowProxy:

    """A DBus proxy for an Evince Window."""
    daemon = None
    bus = None

    def __init__(self, uri, editor, spawn=False, logger=None):
        self._log = logger
        self.uri = uri
        self.uri_unquoted = unquote(uri)
        self.editor = editor
        self.status = CLOSED
        self.source_handler = None
        self.dbus_name = ''
        self._handler = None

        try:
            if EvinceWindowProxy.bus is None:
                EvinceWindowProxy.bus = dbus.SessionBus()

            if EvinceWindowProxy.daemon is None:
                EvinceWindowProxy.daemon = \
                    EvinceWindowProxy.bus.get_object(
                        EV_DAEMON_NAME,
                        EV_DAEMON_PATH,
                        follow_name_owner_changes=True
                    )

            EvinceWindowProxy.bus.add_signal_receiver(
                self._on_doc_loaded,
                signal_name='DocumentLoaded',
                dbus_interface=EV_WINDOW_IFACE,
                sender_keyword='sender'
            )
            self._get_dbus_name(False)
        except dbus.DBusException:
            traceback.print_exc()
            if self._log:
                self._log.debug("Could not connect to the Evince Daemon")

    def _on_doc_loaded(self, uri, **keyargs):
        if (
            unquote(uri) == self.uri_unquoted and
            self._handler is None
        ):
            self.handle_find_document_reply(keyargs['sender'])

    def _get_dbus_name(self, spawn):
        EvinceWindowProxy.daemon.FindDocument(
            self.uri,
            spawn,
            reply_handler=self.handle_find_document_reply,
            error_handler=self.handle_find_document_error,
            dbus_interface=EV_DAEMON_IFACE
        )

    def handle_find_document_error(self, error):
        if self._log:
            self._log.debug("FindDocument DBus call has failed")

    def handle_find_document_reply(self, evince_name):
        if self._handler is not None:
            handler = self._handler
        else:
            handler = self.handle_get_window_list_reply
        if evince_name != '':
            self.dbus_name = evince_name
            self.status = RUNNING
            self.evince = EvinceWindowProxy.bus.get_object(
                self.dbus_name, EVINCE_PATH
            )

            self.evince.GetWindowList(
                dbus_interface=EVINCE_IFACE,
                reply_handler=handler,
                error_handler=self.handle_get_window_list_error
            )

    def handle_get_window_list_error(self, e):
        if self._log:
            self._log.debug("GetWindowList DBus call has failed")

    def handle_get_window_list_reply(self, window_list):
        if len(window_list) > 0:
            window_obj = EvinceWindowProxy.bus.get_object(
                self.dbus_name, window_list[0]
            )
            self.window = dbus.Interface(window_obj, EV_WINDOW_IFACE)
            self.window.connect_to_signal("SyncSource", self.on_sync_source)
        else:
            # This should never happen.
            if self._log:
                self._log.debug("GetWindowList returned empty list")

    def on_sync_source(self, input_file, source_link, _):
        print(input_file + ":" + str(source_link[0]))
        input_file = unquote(input_file)
        cmd = self.editor.replace('%f', input_file)
        cmd = cmd.replace('%l', str(source_link[0]))
        print(cmd)
        subprocess.call(cmd, shell=True)
        if self.source_handler is not None:
            self.source_handler(input_file, source_link, time)


# This file offers backward search in any editor.
#  evince_dbus pdf_file line_source input_file
if __name__ == '__main__':
    import dbus.mainloop.glib
    import sys
    import os

    def print_usage():
        print("""Usage:
  evince_backward_search pdf_file "editorcmd %f %l"'
    %f ... TeX-file to load
    %l ... line to jump to
E.g.:
  evince_backward_search somepdf.pdf "gvim --servername somepdf --remote-silent '+%l<Enter>' %f"
  evince_backward_search somepdf.pdf "emacsclient -a emacs --no-wait +%l %f"
  evince_backward_search somepdf.pdf "scite %f '-goto:%l'"
  evince_backward_search somepdf.pdf "lyxclient -g %f %l"
  evince_backward_search somepdf.pdf "kate --use --line %l"
  evince_backward_search somepdf.pdf "kile --line %l" """)
        sys.exit(1)

    if len(sys.argv) != 3:
        print_usage()

    pdf_file = os.path.abspath(sys.argv[1])

    if not os.path.isfile(pdf_file):
        print_usage()

    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    EvinceWindowProxy(
        # The PDF file name MUST be URI-encoded
        # RFC 1738: unreserved = alpha | digit | safe | extra
        #           safe       = "$" | "-" | "_" | "." | "+"
        #           extra      = "!" | "*" | "'" | "(" | ")" | ","
        'file://' + quote(pdf_file, "/$+!*'(),@=~"),
        sys.argv[2],
        True
    )

    try:
        import gobject
        loop = gobject.MainLoop()
    except ImportError:
        from gi.repository import GLib
        loop = GLib.MainLoop()
    loop.run()
# ex:ts=4:et:
